/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

package org.unidata.mdm.data.service.segments;

import java.util.Objects;

import org.unidata.mdm.core.service.MetaModelService;
import org.unidata.mdm.core.type.data.Attribute;
import org.unidata.mdm.core.type.data.Attribute.AttributeType;
import org.unidata.mdm.core.type.data.DataRecord;
import org.unidata.mdm.core.type.data.MeasuredValue;
import org.unidata.mdm.core.type.data.SimpleAttribute;
import org.unidata.mdm.core.type.data.SimpleAttribute.SimpleDataType;
import org.unidata.mdm.core.type.data.impl.MeasuredSimpleAttributeImpl;
import org.unidata.mdm.core.type.model.AttributeElement;
import org.unidata.mdm.core.type.model.EntityElement;
import org.unidata.mdm.core.type.model.MeasurementCategoryElement;
import org.unidata.mdm.core.type.model.MeasurementUnitElement;
import org.unidata.mdm.data.exception.DataExceptionIds;
import org.unidata.mdm.meta.configuration.Descriptors;
import org.unidata.mdm.system.exception.PlatformBusinessException;

/**
 * Measured attributes normalizer
 */
public interface MeasurementMetaSettingSupport {
    /**
     * Meta model service
     */
    MetaModelService metaModelService();

    default void normalize(DataRecord record, EntityElement entity) {

        record.getAttributesRecursive().stream()
            .filter(attr -> attr.getAttributeType() == AttributeType.SIMPLE)
            .filter(attr -> isMeasured(attr, entity))
            .map(this::toMeasured)
            .filter(Objects::nonNull)
            .filter(attr -> !attr.getValue().isComplete())
            .forEach(attr -> normalize(attr, entity.getAttributes().get(attr.toModelPath())));
    }

    default void normalize(SimpleAttribute<MeasuredValue> attribute, AttributeElement element) {

        if (Objects.isNull(element) || !element.isMeasured()) {

            throw new PlatformBusinessException("Attribute [{}] settings not found or not a measured attribute settings!",
                    DataExceptionIds.EX_DATA_UPSERT_MEASUREMENT_VALUE_UNAVAILABLE, attribute.toModelPath());
        }

        MeasurementCategoryElement mce = metaModelService()
                .instance(Descriptors.MEASUREMENT_UNITS)
                .getCategory(element.getMeasured().getCategoryId());

        if (mce == null) {
            throw new PlatformBusinessException("Measurement category [{}] doesn't exist in the model!",
                    DataExceptionIds.EX_MEASUREMENT_VALUE_DOESNT_EXIST, element.getMeasured().getCategoryId());
        }

        MeasurementUnitElement unit;
        if (attribute.getValue().hasMetadata()) {

            // 1. Check value and unit
            boolean isSameCategory = attribute.getValue().getCategoryId().equals(mce.getId());
            if (!isSameCategory) {
                throw new PlatformBusinessException("Measured value attribute has different units category [{}], than that, defined by the model [{}].",
                        DataExceptionIds.EX_DATA_UPSERT_WRONG_MEASUREMENT_VALUES, attribute.getValue().getCategoryId(), mce.getId());
            }

            unit = mce.getUnit(attribute.getValue().getUnitId());
            if (Objects.isNull(unit)) {
                throw new PlatformBusinessException("Measurement units category from attribute [{}] doesn't contain unit definition [{}].",
                        DataExceptionIds.EX_DATA_UPSERT_MEASUREMENT_UNIT_UNAVAILABLE, attribute.toModelPath(), attribute.getValue().getUnitId());
            }
        } else {

            // 2. Set base
            attribute.getValue()
                .withUnitId(mce.getBaseUnit().getId())
                .withCategoryId(mce.getId());

            unit = mce.getBaseUnit();
        }

        // 3. Set base value if possible
        if (attribute.getValue().hasInitialValue() && !attribute.getValue().hasBaseValue()) {
            unit.process(attribute.getValue());
        }
    }

    private SimpleAttribute<MeasuredValue> toMeasured(Attribute attr) {

        SimpleAttribute<?> sattr = attr.narrow();
        if (sattr.getDataType() == SimpleDataType.MEASURED) {
            return sattr.narrow();
        } else if (sattr.getDataType() == SimpleDataType.NUMBER) {
            SimpleAttribute<Double> orig = sattr.narrow();
            SimpleAttribute<MeasuredValue> nv = new MeasuredSimpleAttributeImpl(orig);
            DataRecord holder = orig.getRecord();
            holder.removeAttribute(orig.getName());
            holder.addAttribute(nv);
            return nv;
        }

        return null;
    }

    private boolean isMeasured(Attribute attr, EntityElement el) {
        AttributeElement element = el.getAttributes().get(attr.toModelPath());
        return attr.getAttributeType() == AttributeType.SIMPLE && element != null && element.isMeasured();
    }
}
